# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2021/9/30 10:09
# @Author  : zuodengbo
# @Email   : zuodb@amoydx.com
# @File    : views
# @Software: PyCharm
import io

from tortoise.query_utils import Q

from user.function import query_user_info, bind_user_role, get_user_role, get_user_role_permissions, roles_or_perms
from utils.const import UserStatus, DefaultConst, RoleAndAuthorityStatus, UserJobStatus, PermissionType
from user.models import *
from user.schemas import *
from utils.common import time_now, active_time, check_phone_number, check_email
from utils.decorator import login_required, check_token
from utils.page_helper import page_helper
from utils.resp_code import *
from utils.send_email import mail
from utils.send_sms import send_sms
from utils.verify_code import verify_the_code, gen_verify_code, generate_captcha
from utils.redis_utils import sanic_redis

import json
import time

import jwt
from pymysql import DatabaseError
from sanic import Blueprint
from sanic.log import logger
from sanic_validation import validate_json, validate_args

user_blueprint = Blueprint("user_views", url_prefix="/user")


@user_blueprint.route("/verify-code", methods=["GET"])
async def get_verify_code(request):
    """
    获取登录验证码
    :param request:
    :return:
    """
    logger.info(request.remote_addr)
    if not request.method == "GET":
        make_method_not_allowed_response()

    code, gen_result = await gen_verify_code()
    if code != RespCode.SUCCESS:
        return make_error_response(code, gen_result)

    return make_success_response(data=gen_result)


@user_blueprint.route("/get-captcha", methods=["GET"])
async def get_captcha(request):
    """
    图片验证码
    :return:
    """
    logger.info(request.remote_addr)
    if request.method != "GET":
        return make_method_not_allowed_response()
    # 获取图片验证码数据
    code, img, file_name = generate_captcha()

    valid_time = active_time()
    try:
        # 将验证码存入数据库
        await VerifyCode.create(key=code.lower(), code=code, valid_time=valid_time)

        # 将验证码存入Redis缓存
        redis_pool = await sanic_redis.redis_pool()
        await redis_pool.set(key=code.lower(), value=code, expire=DefaultConst.verify_code_expired_time)
        # sanic_redis.close()
    except DatabaseError as e:
        msg = "Database error: {} failed: {} line: {}".format(code, e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Save verify code: {} failed: {} line: {}".format(code, e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.SAVE_VERIFY_CODE_FAILED, message=msg)

    # 将图片保存到缓存中
    buffer = io.BytesIO()
    img.save(buffer, 'PNG')
    # # 读取图片的byte值
    # image_data = buffer.getvalue()
    # buffer.close()
    buffer.seek(0)

    # 访问图片，并作为一个响应给前台
    resp = await make_file_response(file_name)
    resp.mime_type = "image/png"

    return resp


@user_blueprint.route("/register", methods=["POST"])
@validate_json(REGISTER_SCHEMA)
async def user_register(request):
    """
    用户注册
    :param request:
    :return:
    """
    logger.info("User Register Data: {}".format(request.json))
    username = request.json.get("username")
    # 传入md5之后的密码 如：123456 -> e10adc3949ba59abbe56e057f20f883e
    name = request.json.get("name")
    password = request.json.get("password")
    phone = request.json.get("phone")
    email = request.json.get("email")
    verify_code = request.json.get("verify_code")

    # 校验验证码
    verify_result = await verify_the_code(verify_code)
    verify_result_body = verify_result.body.decode()
    verify_result_body = json.loads(verify_result_body)
    if verify_result_body["code"] != RespCode.SUCCESS:
        return verify_result

    # 查询用户名、手机号、邮箱是否存在，不存在再创建，已禁用账户不再重新注册
    query_user = await User.filter(
        Q(Q(username=username) | Q(phone=phone) | Q(email=email))).first()
    if query_user:
        if query_user.status == UserStatus.enabled:
            if query_user.username == username:
                msg = "The username: {} already exist !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.USERNAME_ALREADY_EXISTS, message=msg)
            elif query_user.phone == phone:
                msg = "The phone number: {} had been registered !".format(phone)
                logger.warning(msg)
                return make_error_response(code=RespCode.PHONE_REGISTERED, message=msg)
            elif query_user.email == email:
                msg = "The email: {} had been registered !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.EMAIL_REGISTERED, message=msg)
        else:
            if query_user.username == username:
                msg = "The username: {} is disabled !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.USERNAME_ABANDONED, message=msg)
            elif query_user.phone == phone:
                msg = "The user associated with the phone number: {} is disabled !".format(phone)
                logger.warning(msg)
                return make_error_response(code=RespCode.PHONE_DISABLED, message=msg)
            elif query_user.email == email:
                msg = "The user associated with the email: {} is disabled !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.EMAIL_DISABLED, message=msg)

    try:
        user = await User.create(
            username=username,
            name=name,
            password=password,
            phone=phone,
            email=email,
            update_time=time_now()
        )
        msg = "User:{} registered success !".format(username)
        logger.info(msg)
    except DatabaseError as e:
        msg = "Database error：{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Add user: {} info failed：{}, line: {}".format(username, e, e.__traceback__.tb_lineno)
        logger.error(msg)
        # rollback()
        return make_error_response(code=RespCode.USER_REGISTER_FAILED, message=msg)

    _init_data = dict()
    _init_data["username"] = username
    _init_data["user_id"] = user.id

    return make_success_response(message="Register success !", data=_init_data)


@user_blueprint.route("/login", methods=["POST"])
@validate_json(LOGIN_SCHEMA)
async def user_login(request):
    """
    用户登录
    :param request: username, password, verify_code
    :return: code, message, data
    """
    username = request.json.get("username")
    # 传入md5之后的密码 如：123456 -> e10adc3949ba59abbe56e057f20f883e
    password = request.json.get("password")
    verify_code = request.json.get("verify_code")

    # 校验验证码
    verify_result = await verify_the_code(verify_code)
    verify_result_body = verify_result.body.decode()
    verify_result_body = json.loads(verify_result_body)
    if verify_result_body["code"] != RespCode.SUCCESS:
        return verify_result

    # 查询用户名密码是否正确
    _init_data = dict()
    user_dict = dict()
    try:
        logger.info(request.id)

        user = await User.filter(
            Q(Q(username=username) | Q(phone=username) | Q(email=username)),
            status=UserStatus.enabled).values()
        if not user:
            msg = "Username: {} is not exist".format(username)
            logger.warning(msg)
            return make_error_response(code=RespCode.USER_NOT_EXISTS, message=msg)
        user_data = user[0]
        passwd = user_data["password"]
        user_id = user_data["id"]
        name = user_data["name"]
        username = user_data["username"]
        if passwd != password:
            msg = "The password is incorrect !"
            logger.warning(msg)
            return make_error_response(code=RespCode.PASSWD_INCORRECT, message=msg)

        user_role = await get_user_role(user_id)

        role_code = user_role.role.code

        user_dict["user_id"] = user_id
        user_dict["username"] = username
        user_dict["name"] = name
        user_dict["role_code"] = role_code
        _init_data.update(user_dict)

        request.ctx.session["user_info"] = user_dict

        # 登录日志记录
        await LoginLog.create(user_id=user_id, login_time=time_now())

        _init_data["exp"] = int(time.time() + DefaultConst.token_expired_time)
        # 生成token
        token = jwt.encode(_init_data, request.app.config.FORWARDED_SECRET)
        _init_data["access_token"] = token

        logger.debug("User：{} login success!".format(username))

    except DatabaseError as e:
        msg = "Database error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Other failure occurred when login:error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)

    return make_success_response(message="Login Success !", data=_init_data)


@user_blueprint.route("/phone-login", methods=["POST"])
@validate_json(PHONE_LOGIN_SCHEMA)
async def user_phone_login(request):
    """
    手机号登录
    :param request: phone, sms_code
    :return: code, message, data
    """
    phone = request.json.get("phone")
    sms_code = request.json.get("sms_code")

    # 校验验证码
    verify_result = await verify_the_code(sms_code)
    verify_result_body = verify_result.body.decode()
    verify_result_body = json.loads(verify_result_body)
    if verify_result_body["code"] != RespCode.SUCCESS:
        return verify_result

    _init_data = dict()
    user_dict = dict()
    try:
        user = await User.filter(phone=phone, status=UserStatus.enabled).first()
        if not user:
            msg = "Phone number: {} is not exist or disabled".format(phone)
            logger.warning(msg)
            return make_error_response(code=RespCode.USER_NOT_EXISTS, message=msg)
        user_id = user.id
        username = user.username

        user_role = await get_user_role(user_id)

        role_code = user_role.role.code

        user_dict["user_id"] = user_id
        user_dict["username"] = username
        user_dict["name"] = user.name
        user_dict["role_code"] = role_code
        _init_data.update(user_dict)

        request.ctx.session["user_info"] = user_dict

        # 登录日志记录
        await LoginLog.create(user_id=user_id, login_time=time_now())

        _init_data["exp"] = int(time.time() + DefaultConst.token_expired_time)
        # 生成token
        token = jwt.encode(_init_data, request.app.config.FORWARDED_SECRET)
        _init_data["access_token"] = token

        logger.debug("User：{} login success!".format(username))

    except DatabaseError as e:
        msg = "Database error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Other failure occurred when login:error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)

    return make_success_response(message="Login Success !", data=_init_data)


@user_blueprint.route("/logout", methods=["GET"])
@login_required
async def user_logout(request):
    """
    用户登出
    :param request:
    :return:
    """
    # 从token中解析用户
    is_login, data = check_token(request)
    user_id = data["user_id"]

    request.headers.pop("access_token")
    logout = await LoginLog.filter(user_id=user_id).order_by("-id").first()
    logout.logout_time = time_now()
    await logout.save()
    return make_success_response(message="User logout success !")


@user_blueprint.route("/add-user", methods=["POST"])
@validate_json(ADD_USER_SCHEMA)
async def add_user(request):
    """
    添加用户
    :param request:
    :return:
    """
    logger.info("User Register Data: {}".format(request.json))
    username = request.json.get("username")
    # 传入md5之后的密码 如：123456 -> e10adc3949ba59abbe56e057f20f883e
    name = request.json.get("name")
    password = request.json.get("password")
    phone = request.json.get("phone")
    email = request.json.get("email")

    # 查询用户名、手机号、邮箱是否存在，不存在再创建，已禁用账户不再重新创建
    query_user = await User.filter(
        Q(Q(username=username) | Q(phone=phone) | Q(email=email))).first()
    if query_user:
        if query_user.status == UserStatus.enabled:
            if query_user.username == username:
                msg = "The username: {} already exist !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.USERNAME_ALREADY_EXISTS, message=msg)
            elif query_user.phone == phone:
                msg = "The phone number: {} had been registered !".format(phone)
                logger.warning(msg)
                return make_error_response(code=RespCode.PHONE_REGISTERED, message=msg)
            elif query_user.email == email:
                msg = "The email: {} had been registered !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.EMAIL_REGISTERED, message=msg)
        else:
            if query_user.username == username:
                msg = "The username: {} had been abandoned !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.USERNAME_ABANDONED, message=msg)
            elif query_user.phone == phone:
                msg = "The user associated with the phone number: {} had been abandoned !".format(phone)
                logger.warning(msg)
                return make_error_response(code=RespCode.PHONE_DISABLED, message=msg)
            elif query_user.email == email:
                msg = "The user associated with the email: {} had been abandoned !".format(username)
                logger.warning(msg)
                return make_error_response(code=RespCode.EMAIL_DISABLED, message=msg)

    try:
        user = await User.create(
            username=username,
            name=name,
            password=password,
            phone=phone,
            email=email,
            update_time=time_now()
        )
        msg = "Add user:{} success !".format(username)
        logger.debug(msg)

    except DatabaseError as e:
        msg = "Database error：{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Add user: {} info failed：{}, line: {}".format(username, e, e.__traceback__.tb_lineno)
        logger.error(msg)
        # rollback()
        return make_error_response(code=RespCode.USER_REGISTER_FAILED, message=msg)

    _init_data = dict()
    _init_data["username"] = username
    _init_data["user_id"] = user.id

    return make_success_response(message="Add user success !", data=_init_data)


@user_blueprint.route("/get-my-info", methods=["GET"])
@login_required
async def get_my_info(request):
    """
    获取我的信息
    :param request:
    :return:
    """
    # 从token中解析用户
    is_login, data = check_token(request)
    user_id = data["user_id"]

    # session中取用户信息, 服务重启会导致session丢失
    # user_info = request.ctx.session.get("user_info")
    # user_id = user_info.get("user_id")
    # logger.info(user_info)

    my_info = await query_user_info(user_id)
    if not my_info:
        msg = "Query my info failed !"
        return make_error_response(code=RespCode.QUERY_FAILED, message=msg)
    return make_success_response(message="Get my info success !", data=my_info)


@user_blueprint.route("/update-my-info", methods=["PUT"])
@login_required
@validate_json(USER_INFO_SCHEMA)
async def update_my_info(request):
    """
    更新用户信息
    :param request:
    :return:
    """
    logger.info("Update User Data: {}".format(request.json))
    # 从token中解析用户
    is_login, data = check_token(request)
    user_id = data["user_id"]
    username = data["username"]
    try:
        user_info = request.json.get("user_info")
        job_info = request.json.get("job_info")

        phone = user_info.get("phone")
        birthday = user_info.get("birthday")
        age = user_info.get("age")
        gender = user_info.get("gender")
        address = user_info.get("address")
        company = job_info.get("company")
        group_address = job_info.get("group_address")
        department = job_info.get("department")
        position = job_info.get("position")
        position_level = job_info.get("position_level")
        job_num = job_info.get("job_num")
        job_status = job_info.get("job_status")

        if not phone:
            msg = "Phone number can't be null"
            logger.error(msg)
            return make_error_response(RespCode.INVALID_PHONE, msg)

        if not check_phone_number(phone):
            msg = "Invalid phone number: {} !".format(phone)
            logger.error(msg)
            return make_error_response(RespCode.INVALID_PHONE, msg)

        user = await User.get(id=user_id)

        # 公司 部门 职位
        if company and department and position:
            company_obj = await Company.filter(name=company).first()
            if not company_obj:
                logger.warning("Company:{} is not exist, now is adding...".format(company))
                company_obj = await Company.create(name=company, address=group_address)

            department_obj = await Department.filter(name=department).first()
            if not department_obj:
                logger.warning("Department:{} is not exist, now is adding...".format(department))
                department_obj = await Department.create(name=department)

            position_obj = await Position.filter(name=position).first()
            if not position_obj:
                logger.warning("Position:{} is not exist, now is adding...".format(position))
                position_obj = await Position.create(name=position)

            await position_obj.save()

            user_job = await UserJobInfo.filter(user=user, company=company_obj,
                                                status=UserJobStatus.mission).first()
            if user_job:
                user_job.department_id = department_obj.id
                user_job.position_id = position_obj.id
                user_job.job_num = job_num
                user_job.status = job_status
                user_job.level = position_level
                user_job.update_time = time_now()
                await user_job.save()
            else:
                await UserJobInfo.create(user=user, company=company_obj,
                                         department=department_obj, position=position_obj,
                                         job_num=job_num, status=job_status,
                                         level=position_level, update_time=time_now())

        # 用户信息
        user.phone = phone
        user.age = age
        user.gender = gender
        user.birthday = birthday
        user.address = address
        user.update_time = time_now()
        await user.save()

        return make_success_response(message="Update user:{} info success !".format(username))

    except Exception as e:
        msg = "When update user:{} info, failure occurred:{}, line: {}". \
            format(username, e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/user-list", methods=["GET"])
@login_required
@validate_args(COMMON_SCHEMA)
async def search_users(request):
    """
    查询用户信息
    :param request:
    :return:
    """
    keyword = request.args.get("keyword")
    offset, limit = page_helper(request)
    _init_data = dict()
    user_info = list()

    try:
        if keyword:
            # contains需要精确大小写，icontains忽略大小写
            condition = Q(Q(id__icontains=keyword) | Q(username__icontains=keyword)
                          | Q(name__icontains=keyword) | Q(email__icontains=keyword)
                          | Q(phone__icontains=keyword) | Q(age__icontains=keyword)
                          | Q(gender__icontains=keyword) | Q(address__icontains=keyword)
                          | Q(remark__icontains=keyword)) & Q(status=RoleAndAuthorityStatus.available)
            users = await User.filter(condition).all().order_by("id").offset(offset).limit(limit)
            total = await User.filter(condition).all().count()
        else:
            users = await User.filter().all().order_by("id").offset(offset).limit(limit)
            total = await User.filter().all().count()
        for user in users:
            _init_data = user.__json__()
            user_info.append(_init_data)
    except Exception as e:
        msg = "Query users info failed:{} line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.QUERY_FAILED, message=msg)

    return make_success_response(data=user_info, total=total, query_total=users.__len__())


@user_blueprint.route("/get-user-info", methods=["GET"])
@login_required
@validate_args(COMMON_ID_SCHEMA)
async def get_user_info(request):
    """
    查询单个用户信息
    :param request:
    :return:
    """
    try:
        user_id = request.args.get("id")

        user_info = await query_user_info(user_id)
        if not user_info:
            msg = "Query User Info Failed !"
            logger.warning(msg)
            return make_error_response(code=RespCode.QUERY_FAILED, message=msg)

    except Exception as e:
        msg = "Query users info failed:{} line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.QUERY_FAILED, message=msg)

    return make_success_response(data=user_info)


@user_blueprint.route("/modify-user-info", methods=["POST"])
@login_required
@validate_json(USER_INFO_SCHEMA)
async def modify_user_info(request):
    """
    修改用户信息
    :param request:
    :return:
    """
    logger.info("Modify my info data: {}".format(request.json))

    try:
        role_info = request.json.get("role_info")
        user_info = request.json.get("user_info")
        job_info = request.json.get("job_info")
        user_id = user_info.get("id")

        phone = user_info.get("phone")
        birthday = user_info.get("birthday")
        age = user_info.get("age")
        gender = user_info.get("gender")
        address = user_info.get("address")
        status = user_info.get("status")
        company = job_info.get("company")
        group_address = job_info.get("group_address")
        department = job_info.get("department")
        position = job_info.get("position")
        position_level = job_info.get("position_level")
        job_num = job_info.get("job_num")
        job_status = job_info.get("job_status")
        role_name = role_info.get("role_name")

        if not phone:
            msg = "Phone number can't be null"
            logger.error(msg)
            return make_error_response(RespCode.INVALID_PHONE, msg)

        if not check_phone_number(phone):
            msg = "Invalid phone number: {} !".format(phone)
            logger.error(msg)
            return make_error_response(RespCode.INVALID_PHONE, msg)

        user = await User.get(id=user_id)
        username = user.username

        # 公司 部门 职位
        if company and department and position:
            company_obj = await Company.filter(name=company).first()
            if not company_obj:
                logger.warning("Company:{} is not exist, now is adding...".format(company))
                company_obj = await Company.create(name=company, address=group_address)

            department_obj = await Department.filter(name=department).first()
            if not department_obj:
                logger.warning("Department:{} is not exist, now is adding...".format(department))
                department_obj = await Department.create(name=department)

            position_obj = await Position.filter(name=position).first()
            if not position_obj:
                logger.warning("Position:{} is not exist, now is adding...".format(position))
                position_obj = await Position.create(name=position)

            await position_obj.save()

            user_job = await UserJobInfo.filter(user=user, company=company_obj,
                                                status=UserJobStatus.mission).first()
            if user_job:
                user_job.department_id = department_obj.id
                user_job.position_id = position_obj.id
                user_job.job_num = job_num
                user_job.status = job_status
                user_job.level = position_level
                user_job.update_time = time_now()
                await user_job.save()
            else:
                await UserJobInfo.create(user=user, company=company_obj,
                                         department=department_obj, position=position_obj,
                                         job_num=job_num, status=job_status,
                                         level=position_level, update_time=time_now())

        # 绑定角色，失败也没有关系
        if role_name:
            await bind_user_role(username, role_name)

        # 用户信息
        user.phone = phone
        user.age = age
        user.gender = gender
        user.birthday = birthday
        user.address = address
        user.status = status
        user.update_time = time_now()
        await user.save()

        return make_success_response(message="Update user：{} info success !".format(username))

    except Exception as e:
        msg = "When update user info, failure occurred:{}, line: {}". \
            format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/get-user-job-info", methods=["GET"])
@login_required
@validate_args(COMMON_ID_SCHEMA)
async def get_user_job_info(request):
    """
    查询用户信息
    前端状态映射：在职-1， 离职-0
    :param request:
    :return:
    """
    _init_data = dict()
    try:
        user_id = request.args.get("id")
        user_job = await UserJobInfo.filter(user__id=user_id, status=UserJobStatus.mission). \
            values("user__username", "user__name", "company__name",
                   "department__name", "position__name",
                   "level", "create_time", "update_time")
        if user_job:
            # 返回字段处理
            user_job_info = json_format(user_job[0], default=str)
            user_job_info["username"] = user_job_info.pop("user__username")
            user_job_info["name"] = user_job_info.pop("user__name")
            user_job_info["company"] = user_job_info.pop("company__name")
            user_job_info["department"] = user_job_info.pop("department__name")
            user_job_info["position"] = user_job_info.pop("position__name")
            user_job_info["position_level"] = user_job_info.pop("level")
            user_job_info["create_time"] = \
                user_job_info["create_time"][:user_job_info["create_time"].rfind(".")]
            user_job_info["update_time"] = \
                user_job_info["update_time"][:user_job_info["update_time"].rfind(".")]
            _init_data.update(user_job_info)
        return make_success_response(data=_init_data)
    except Exception as e:
        msg = "Query user job info failed: error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.QUERY_FAILED, message=msg)


@user_blueprint.route("/add-role", methods=["POST"])
@login_required
@validate_json(ROLE_SCHEMA)
async def add_role_info(request):
    """
    创建角色信息
    :param request:
    :return:
    """
    # 从token中解析用户
    is_login, data = check_token(request)
    user_id = data["user_id"]

    code = request.json.get("code")
    role_name = request.json.get("role_name")
    status = request.json.get("status", RoleAndAuthorityStatus.available)

    try:
        role = await Role.filter(Q(name=role_name) | Q(code=code) |
                                 Q(name=role_name, code=code)).first()
        if role:
            if role.status == RoleAndAuthorityStatus.available:
                msg = "Role: {} already exist".format(role_name)
                logger.warning(msg)
                return make_error_response(code=RespCode.ROLE_ALREADY_EXISTS, message=msg)
            else:
                role.status = RoleAndAuthorityStatus.available
                await role.save()

        await Role.create(name=role_name, code=code, creator_id=user_id, status=status)

        return make_success_response(message="Create role info success !")

    except DatabaseError as e:
        msg = "Database error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Add role failed: error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)


@user_blueprint.route("/role-list", methods=["GET"])
@login_required
@validate_args(COMMON_SCHEMA)
async def get_roles(request):
    """
    获取角色信息
    :param request:
    :return:
    """
    if request.method != "GET":
        return make_method_not_allowed_response()
    keyword = request.args.get("keyword")
    offset, limit = page_helper(request)
    _init_data = dict()
    role_info = list()

    try:
        if keyword:
            # contains需要精确大小写，icontains忽略大小写
            condition = Q(Q(id__icontains=keyword) | Q(name__icontains=keyword)
                          | Q(code__icontains=keyword) | Q(creator_id__icontains=keyword)
                          ) & Q(status=RoleAndAuthorityStatus.available)
            roles = await Role.filter(condition).all().offset(offset).limit(limit)
            total = await Role.filter(condition).all().count()
        else:
            roles = await Role.filter().all().offset(offset).limit(limit)
            total = await Role.filter().all().count()

        for role in roles:
            _init_data = role.__json__()
            role_info.append(_init_data)

        msg = "Query roles info success !"
        return make_success_response(message=msg, data=role_info, total=total)

    except DatabaseError as e:
        msg = "Database error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Query role failed: error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)


@user_blueprint.route("/get-role-info", methods=["GET"])
@login_required
@validate_args(COMMON_ID_SCHEMA)
async def get_role_info(request):
    """
    获取角色详细信息
    :param request:
    :return:
    """
    if request.method != "GET":
        return make_method_not_allowed_response()
    role_id = request.args.get("id")
    
    try:
        role = await Role.get(id=role_id)
        role_data = role.__json__()
        
        return make_success_response(message="Get role info success !", data=role_data)
    except Exception as e:
        msg = "Query role info failed: {}".format(e)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/modify-role", methods=["PUT"])
@login_required
@validate_json(ROLE_INFO_SCHEMA)
async def update_role_info(request):
    """
    修改角色信息
    :param request:
    :return:
    """
    json_data = request.json
    role_id = int(json_data.get("id"))
    name = json_data.get("name")
    code = json_data.get("code")
    creator = int(json_data.get("creator"))
    status = json_data.get("status")
    update_time = time_now()

    try:
        await Role.filter(id=role_id).update(
            name=name, code=code, creator_id=creator,
            status=status, update_time=update_time)

        return make_success_response(message="Update role info success !")
    except Exception as e:
        msg = "Update role info failed: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/delete-role", methods=["DELETE"])
@validate_json(COMMON_ID_SCHEMA)
@login_required
async def delete_user(request):
    """
    删除角色
    :param request:
    :return:
    """
    try:
        role_id = request.json.get("id")
        await Role.filter(id=role_id).update(status=UserStatus.disabled)
        return make_success_response(message="Delete Role Success")
    except Exception as e:
        msg = "Delete role failed: {}".format(e)
        logger.error(msg)
        return make_error_response(RespCode.UNKNOWN_FAILURE, msg)


@user_blueprint.route("/bind-user-role", methods=["POST"])
@login_required
@validate_json(USER_ROLE_SCHEMA)
async def add_user_role(request):
    """
    创建用户角色信息
    :param request:
    :return:
    """
    username = request.json.get("username")
    role_name = request.json.get("role_name")
    if not role_name:
        msg = "Role name is null !"
        return make_error_response(code=RespCode.ROLE_NOT_EXISTS, message=msg)

    code, msg = await bind_user_role(username, role_name)
    if code == RespCode.SUCCESS:
        return make_success_response(message=msg)
    else:
        return make_error_response(code=code, message=msg)


@user_blueprint.route("/authority-list", methods=["GET"])
@login_required
@validate_args(COMMON_SCHEMA)
async def get_authorities(request):
    """
    获取权限信息
    :param request:
    :return:
    """
    if request.method != "GET":
        return make_method_not_allowed_response()
    keyword = request.args.get("keyword")
    offset, limit = page_helper(request)
    _init_data = dict()
    perm_info = list()

    try:
        if keyword:
            # contains需要精确大小写，icontains忽略大小写
            condition = Q(Q(id__icontains=keyword) | Q(name__icontains=keyword)
                          | Q(code__icontains=keyword) | Q(path__icontains=keyword)
                          | Q(uri__icontains=keyword) | Q(icon__icontains=keyword)
                          | Q(type__icontains=keyword) | Q(parent_id__icontains=keyword)
                          ) & Q(status=RoleAndAuthorityStatus.available)
            perms = await Authority.filter(condition).all().offset(offset).limit(limit)
            total = await Authority.filter(condition).all().count()
        else:
            perms = await Authority.filter().all().offset(offset).limit(limit)
            total = await Authority.filter().all().count()

        for perm in perms:
            _init_data = perm.__json__()
            perm_info.append(_init_data)

        msg = "Query authorities info success !"
        return make_success_response(message=msg, data=perm_info, total=total)

    except DatabaseError as e:
        msg = "Database error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Query authorities failed: error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)


@user_blueprint.route("/parent-authorities", methods=["GET"])
@login_required
async def get_parent_authorities(request):
    """
    获取父目录信息
    :param request:
    :return:
    """
    if request.method != "GET":
        return make_method_not_allowed_response()
    perm_info = list()

    try:
        perms = await Authority.filter(
            type=PermissionType.catalog, status=RoleAndAuthorityStatus.available).all()

        for perm in perms:
            _init_data = dict()
            _init_data["id"] = perm.__json__().get("id")
            _init_data["name"] = perm.__json__().get("name")
            perm_info.append(_init_data)

        msg = "Query parent authorities info success !"
        return make_success_response(message=msg, data=perm_info)

    except DatabaseError as e:
        msg = "Database error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Query parent authorities failed: error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)


@user_blueprint.route("roles-or-perms", methods=["GET"])
@login_required
@validate_args(ROLE_OR_AUTHORITY_SCHEMA)
async def get_roles_or_authorities(request):
    """
    获取角色或权限
    :param request:
    :return:
    """
    # 从token中解析角色
    is_login, data = check_token(request)
    user_id = data["user_id"]
    role_code = data["role_code"]

    model = request.args.get("model")
    resp_code, data = await roles_or_perms(model, user_id, role_code)
    if resp_code != RespCode.SUCCESS:
        return make_error_response(code=resp_code, message=data)

    msg = "Get {} Data Success".format(model)
    return make_success_response(message=msg, data=data)


@user_blueprint.route("/add-authority", methods=["POST"])
@login_required
@validate_json(AUTHORITY_SCHEMA)
async def add_authority_info(request):
    """
    创建权限信息
    type对应: 1-目录（数据），2-菜单（页面），3-按钮（操作）
    :param request:
    :return:
    """
    logger.debug("Add Authority Data: {}".format(request.json))
    try:
        code = request.json.get("code")
        authority_name = request.json.get("name")
        f_type = int(request.json.get("type"))
        uri = request.json.get("uri")
        path = request.json.get("path")
        icon = request.json.get("icon")
        status = request.json.get("status", RoleAndAuthorityStatus.available)
        parent_id = request.json.get("parent_id", None)

        authority_count = await Authority.filter(
            Q(name=authority_name, status=RoleAndAuthorityStatus.available) |
            Q(uri=uri, status=RoleAndAuthorityStatus.available) |
            Q(code=code, status=RoleAndAuthorityStatus.available)
        ).count()
        if authority_count != DefaultConst.default_count:
            msg = "Authority: {} already exist".format(authority_name)
            logger.warning(msg)
            return make_error_response(code=RespCode.AUTHORITY_NOT_EXISTS, message=msg)

        if parent_id:
            parent_id = int(parent_id)
            await Authority.create(name=authority_name, code=code, uri=uri, type=f_type,
                                   path=path, icon=icon, status=status, parent_id=parent_id)
        else:
            await Authority.create(name=authority_name, code=code, uri=uri, type=f_type,
                                   path=path, icon=icon, status=status)

        return make_success_response(message="Create authority info success !")

    except DatabaseError as e:
        msg = "Database error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Add authority failed: error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)


@user_blueprint.route("/modify-authority", methods=["PUT"])
@login_required
@validate_json(AUTHORITY_INFO_SCHEMA)
async def update_authority_info(request):
    """
    修改权限信息
    :param request:
    :return:
    """
    json_data = request.json
    perm_id = int(json_data.get("id"))
    name = json_data.get("name")
    code = json_data.get("code")
    uri = json_data.get("uri")
    perm_type = int(json_data.get("type"))
    icon = json_data.get("icon")
    path = json_data.get("path")
    parent_id = json_data.get("parent_id")
    status = json_data.get("status")
    update_time = time_now()

    try:
        if parent_id:
            parent_id = int(parent_id)
        await Authority.filter(id=perm_id).update(
            name=name, code=code, parent_id=parent_id,
            uri=uri, type=perm_type, icon=icon, path=path,
            status=status, update_time=update_time)

        return make_success_response(message="Update authority info success !")
    except Exception as e:
        msg = "Update authority info failed: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/get-authority-info", methods=["GET"])
@login_required
@validate_args(COMMON_ID_SCHEMA)
async def get_authority_info(request):
    """
    获取权限详细信息
    :param request:
    :return:
    """
    if request.method != "GET":
        return make_method_not_allowed_response()
    authority_id = request.args.get("id")

    try:
        authority = await Authority.get(id=authority_id)
        authority_data = authority.__json__()

        return make_success_response(message="Get authority info success !", data=authority_data)
    except Exception as e:
        msg = "Query authority info failed: {}".format(e)
        logger.error(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/delete-authority", methods=["DELETE"])
@validate_json(COMMON_ID_SCHEMA)
@login_required
async def delete_user(request):
    """
    删除权限
    :param request:
    :return:
    """
    try:
        perm_id = request.json.get("id")
        await Authority.filter(id=perm_id).update(status=UserStatus.disabled)
        return make_success_response(message="Delete Authority Success")
    except Exception as e:
        msg = "Delete authority failed: {}".format(e)
        logger.error(msg)
        return make_error_response(RespCode.UNKNOWN_FAILURE, msg)


@user_blueprint.route("/add-role-authority", methods=["POST"])
@login_required
@validate_json(ROLE_AUTHORITY_SCHEMA)
async def add_role_authority(request):
    """
    创建角色权限信息
    :param request:
    :return:
    """
    authority_ids = request.json.get("perm_ids")
    role_id = request.json.get("role_id")

    try:
        authority = await Authority.filter(id__in=authority_ids,
                                           status=RoleAndAuthorityStatus.available).all()
        role = await Role.filter(id=role_id,
                                 status=RoleAndAuthorityStatus.available).first()
        if not authority:
            msg = "Authority name: {} not exist".format(authority_ids)
            logger.warning(msg)
            return make_error_response(code=RespCode.AUTHORITY_NOT_EXISTS, message=msg)
        if not role:
            msg = "Role:{} not exist !".format(role_id)
            logger.warning(msg)
            return make_error_response(code=RespCode.ROLE_NOT_EXISTS, message=msg)
        role_perms = list()
        for item in authority:
            role_authority = await RoleAuthority.filter(role=role, authority=item).first()
            if role_authority:
                if role_authority.status == RoleAndAuthorityStatus.available:
                    msg = "Authority:{} have been banded with role:{}".format(item.name, role.name)
                    logger.warning(msg)
                    continue
                if role_authority.status == RoleAndAuthorityStatus.frozen:
                    role_authority.status = RoleAndAuthorityStatus.available
                    await role_authority.save()

            role_perms.append(RoleAuthority(role_id=role_id, authority=item))

        await RoleAuthority.bulk_create(role_perms)
        msg = "Create role:{} authority:{} relationship success !".format(role.name, authority_ids)
        logger.info(msg)
        return make_success_response(message=msg)

    except DatabaseError as e:
        msg = "Database error: {}, line: {}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)
    except Exception as e:
        msg = "Role band authority failed: error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.error(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)


@user_blueprint.route("/change-pwd", methods=["PATCH"])
@validate_json(CHANGE_PWD_SCHEMA)
@login_required
async def user_change_pwd(request):
    """
    用户修改密码
    :param request:
    :return:
    """
    # 传入md5之后的密码 如：123456 -> e10adc3949ba59abbe56e057f20f883e
    old_pwd = request.json.get("old_pwd")
    new_pwd = request.json.get("new_pwd")

    # 从token中解析用户
    is_login, data = check_token(request)
    user_id = data["user_id"]

    # 查询用户名密码是否正确
    if old_pwd == new_pwd:
        return make_success_response(message="Same Password !")
    try:
        # 查询用户
        user = await User.get(id=user_id)
        passwd = user.password

        # 判断输入密码是否和数据库中一致
        if passwd != old_pwd:
            msg = "The old password is incorrect !"
            logger.warning(msg)
            return make_error_response(code=RespCode.PASSWD_INCORRECT, message=msg)
        # 更新密码
        user.password = new_pwd
        await user.save()

    except DatabaseError as e:
        msg = "Database error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)

    except Exception as e:
        msg = "Other failure occurred when change password: error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)

    return make_success_response(message="Change Password Success !")


@user_blueprint.route("/send-verify-code", methods=["POST"])
@validate_json(PHONE_EMAIL_SCHEMA)
async def send_verify_code(request):
    """
    发送手机验证码或邮箱验证码
    :param request:
    :return:
    """
    phone = request.json.get("phone")
    email = request.json.get("email")
    if not phone and not email:
        msg = "Phone number or email must be input one"
        return make_error_response(code=RespCode.REQUIRE_PHONE_OR_EMAIL, message=msg)
    if phone and email:
        msg = "Phone number and  email just need one"
        return make_error_response(code=RespCode.REQUIRE_PHONE_OR_EMAIL, message=msg)

    # 判断是否邮箱
    if email:
        if not check_email(email):
            msg = "Invalid email:{} !".format(email)
            logger.error(msg)
            return make_error_response(RespCode.INVALID_EMAIL, msg)

        code, gen_result = await gen_verify_code(valid_time=DefaultConst.code_valid_time, mode="sms")
        if code != RespCode.SUCCESS:
            return make_error_response(code, gen_result)

        content = "【Borden系统】验证码：{}， 有效时间10分钟".format(gen_result)

        # 发送验证码邮件
        code, msg = mail.send_handle([email], content=content)
        if code != RespCode.SUCCESS:
            logger.error(msg)
            return make_error_response(code, msg)
        return make_success_response(message=msg)

    if phone:
        # 判断手机号
        if not check_phone_number(phone):
            msg = "Invalid phone number: {} !".format(phone)
            logger.error(msg)
            return make_error_response(RespCode.INVALID_PHONE, msg)

        code, gen_result = await gen_verify_code(valid_time=DefaultConst.code_valid_time, mode="sms")
        if code != RespCode.SUCCESS:
            return make_error_response(code, gen_result)

        content = "【Borden系统】验证码：{}， 有效时间10分钟".format(gen_result)

        # 发送验证码短信
        code, msg, sms_id = send_sms(content, phone)
        if code != RespCode.SUCCESS:
            logger.error(msg)
            return make_error_response(code, msg)
        return make_success_response(message=msg)


@user_blueprint.route("/reset-pwd", methods=["POST"])
@validate_json(RESET_PWD_SCHEMA)
async def user_reset_pwd(request):
    """
    用户重置密码
    :param request:
    :return:
    """
    # 传入md5之后的密码 如：123456 -> e10adc3949ba59abbe56e057f20f883e
    phone_or_email = request.json.get("phone_or_email")
    verify_code = request.json.get("verify_code")
    new_pwd = request.json.get("new_pwd")

    # 校验验证码
    verify_result = await verify_the_code(verify_code)
    verify_result_body = verify_result.body.decode()
    verify_result_body = json.loads(verify_result_body)
    if verify_result_body["code"] != RespCode.SUCCESS:
        return verify_result

    try:
        # 查询用户
        user = await User.filter(Q((Q(phone=phone_or_email) | Q(email=phone_or_email)),
                                   status=UserStatus.enabled)).first()
        if not user:
            msg = "User: {} is not exist".format(phone_or_email)
            logger.warning(msg)
            return make_error_response(code=RespCode.USER_NOT_EXISTS, message=msg)
        # 更新密码
        user.password = new_pwd
        await user.save()

    except DatabaseError as e:
        msg = "Database error:{}, line:{}".format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.DATABASE_ERROR, message=msg)

    except Exception as e:
        msg = "Other failure occurred when reset password: error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)

    return make_success_response(message="Reset Password Success !")


@user_blueprint.route("/delete-user", methods=["DELETE"])
@validate_json(COMMON_ID_SCHEMA)
@login_required
async def delete_user(request):
    """
    删除用户
    :param request:
    :return:
    """
    try:
        user_id = request.json.get("id")
        await User.filter(id=user_id).update(status=UserStatus.disabled)
        return make_success_response(message="Delete User Success")
    except Exception as e:
        msg = "Delete user failed: {}".format(e)
        logger.error(msg)
        return make_error_response(RespCode.UNKNOWN_FAILURE, msg)


@user_blueprint.route("/get-user-role-perms", methods=["GET"])
@login_required
async def get_user_role_perms(request):
    """
    获取用户角色权限
    :return:
    """
    try:
        # 从token中解析用户
        is_login, data = check_token(request)
        user_id = data["user_id"]

        perms = dict()
        # 查询用户角色
        user_role = await get_user_role(user_id)
        if not user_role:
            return perms
        # 查询角色权限
        catalogs, path_menus, buttons = await get_user_role_permissions(user_role)
        if not catalogs:
            return perms
        perms["menus"] = catalogs
        perms["buttons"] = buttons

        msg = "Query user role permissions success"
        return make_success_response(message=msg, data=perms)

    except Exception as e:
        msg = "Other failure occurred when query permissions: error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/role-perms", methods=["GET"])
@validate_args(ROLE_OR_AUTHORITY_SCHEMA)
@login_required
async def get_role_perms(request):
    """
    获取角色权限
    :return:
    """
    try:
        model = request.args.get('model')
        role_id = request.args.get("id")
        code, data = await roles_or_perms(model, role_id=role_id)
        if not code == RespCode.SUCCESS:
            return make_error_response(code=RespCode.QUERY_FAILED, message=data)

        msg = "Query role permissions success"
        return make_success_response(message=msg, data=data)

    except Exception as e:
        msg = "Other failure occurred when query permissions: error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)


@user_blueprint.route("/cancel-role-perms", methods=["GET"])
@validate_args(ROLE_AUTHORITY_SCHEMA)
@login_required
async def cancel_role_perms(request):
    """
    获取角色权限
    :return:
    """
    try:
        perm_ids = request.args.get('perms_ids')
        role_id = request.args.get("role_id")
        authority = await Authority.filter(id__in=perm_ids,
                                           status=RoleAndAuthorityStatus.available).all()
        role = await Role.filter(id=role_id,
                                 status=RoleAndAuthorityStatus.available).first()
        if not authority:
            msg = "Authority name: {} not exist".format(perm_ids)
            logger.warning(msg)
            return make_error_response(code=RespCode.AUTHORITY_NOT_EXISTS, message=msg)
        if not role:
            msg = "Role:{} not exist !".format(role_id)
            logger.warning(msg)
            return make_error_response(code=RespCode.ROLE_NOT_EXISTS, message=msg)

        role_perms = list()
        for item in authority:
            role_authority = await RoleAuthority.filter(role=role, authority=item,
                                                        status=RoleAndAuthorityStatus.available).exists()
            if role_authority:
                role_perms.append(item.id)

        await RoleAuthority.filter(role_id=role_id, authority_id__in=role_perms).\
            update(status=RoleAndAuthorityStatus.frozen)

        msg = "Query role permissions success"
        return make_success_response(message=msg)

    except Exception as e:
        msg = "Other failure occurred when query permissions: error: {}, line:{}". \
            format(e, e.__traceback__.tb_lineno)
        logger.warning(msg)
        return make_error_response(code=RespCode.UNKNOWN_FAILURE, message=msg)